//rebuild.cpp
//author: aandriyash

#include "utils.h"

#include <stdio.h>
#include <cstring>

#include <iostream>

int RebuildRAID1(
    int totalDisks, 
    int totalBlocks, 
    int blockSize
) 
{
    int failedCounter = 0;
    
    RETURN_IF_ERROR(Mapping(totalDisks, totalBlocks, blockSize, true));

    for (int i = 1; i < totalDisks + 1; i++) 
    {
        if (NULL == ppMappedDataArray[i]) 
        {
            failedCounter++;
        }
    }
    
    if (failedCounter == totalDisks) 
    {
        std::cout << "All of disks are failed. Can't rebuild RAID.\n";
        return 1;
    }
    
    int validDisk;
    for(int i = 1; i < totalDisks + 1; i++) 
    {
        if (NULL != ppMappedDataArray[i]) 
        {
            validDisk = i; 
            break;
        }
    }

    for (int i = 1; i < totalDisks + 1; i++) 
    {
        if ((NULL != ppMappedDataArray[i]) && (i != validDisk)) 
        {
            if (memcmp(ppMappedDataArray[i], ppMappedDataArray[validDisk], totalBlocks*blockSize) != 0) 
            {
                std::cout << "Disks have different data. Can't rebuild RAID.\n";
                return 1;
            }
        }
    }

    for (int i = 1; i < totalDisks + 1; i++) 
    {
        if ( NULL == ppMappedDataArray[i]) 
        {
            char name[10];
            sprintf(name, "Disk%i", i);
            FILE* pDisk = fopen(name, "wb+");
            
            if (NULL == pDisk) 
            {
                std::cout << "Can't create Disk" << i << ". Can't rebuild RAID.\n";
                return 1;
            }
            
            fwrite(ppMappedDataArray[validDisk], sizeof(char), blockSize * totalBlocks, pDisk);
            fclose(pDisk);
        }
    }
    
    RETURN_IF_ERROR(Unmapping(totalDisks, totalBlocks, blockSize, true));
    
    return 0;
}
int RebuildRAID345(
    int totalDisks, 
    int totalBlocks, 
    int blockSize
) 
{
    RETURN_IF_ERROR(Mapping(totalDisks, totalBlocks, blockSize, true));
    
    int failedCounter = 0;
    
    for (int i = 1; i < totalDisks + 1; i++) 
    {
        if (NULL == ppMappedDataArray[i]) 
        {
            failedCounter++;
        }
    }
    
    if (failedCounter > 1) 
    {
        std::cout << "More than 1 of disks are failedCounter. Can't rebuild RAID.\n";
        return 1;
    }

    int failedDisk;
    for (int i = 1; i < totalDisks + 1; i++) 
    {
        if (NULL == ppMappedDataArray[i]) 
        {
            failedDisk = i; 
            break;
        }
    }
    
    char name[10];    
    sprintf(name, "Disk%i", failedDisk);
    FILE* pDisk = fopen(name, "wb+");
    
    if (NULL == pDisk) 
    {
        std::cout << "Can't create Disk " << failedDisk << ". Can't rebuild RAID.\n";
        return 1;
    }

    for (int i = 0; i < totalBlocks * blockSize; i++) 
    {
        char XORres = 0;
        for (int j = 1; j < totalDisks + 1; j++) 
        {
            if (j != failedDisk) 
            {
                XORres = XORres xor *(ppMappedDataArray[j] + i);
            }
        }
        fwrite(&XORres, sizeof(char), 1, pDisk);
    }
    
    fclose(pDisk);    
    RETURN_IF_ERROR(Unmapping(totalDisks, totalBlocks, blockSize, true));
    return 0;
}

int main(
    int argc, 
    char* argv[]
) 
{
    RaidConfig raidConf;
    raidConf.Read();     
    
    RETURN_IF_ERROR_TRACE(!IsDiskCrashed(raidConf.totalDisks, true), ("No need to rebuild RAID.\n")); 

    int err = 0;
    switch (raidConf.raidLevel)
    {
        case 0:
            std::cout << "RAID0 can't be rebuilded.\n";
            err = 1;
            break;
        case 1:
            err =  RebuildRAID1(raidConf.totalDisks, raidConf.totalBlocks, raidConf.blockSize);
            break;
        default:
            err =  RebuildRAID345(raidConf.totalDisks, raidConf.totalBlocks, raidConf.blockSize);
            break;
    }
    
    return err;
}

